package br.com.naybee.framework.client.models;

import static java.lang.Boolean.parseBoolean;
import static java.lang.Double.parseDouble;
import static java.lang.Float.parseFloat;
import static java.lang.Integer.parseInt;
import static java.lang.Long.parseLong;
import static br.com.naybee.framework.client.utils.ValidatorUtils.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map.Entry;

import br.com.naybee.framework.client.utils.NumberUtils;

@SuppressWarnings({ "serial", "unchecked" })
public class Model extends LinkedHashMap<String, Object> {

	private static final String ERRORS_KEY = "errors";
	private static final String ERROR_CODE_KEY = "code";
	private static final String ERROR_MESSAGE_KEY = "message";
	private static final String ERROR_DETAILS_KEY = "details";

	public Model() {
	}

	public Model getModel(String key) {
		Object objectModel = get(key);
		if (objectModel instanceof LinkedHashMap<?, ?>) {
			Model result = Models.newModel();
			result.putAll((LinkedHashMap<String, ?>) objectModel);
			replace(key, result);
			return result;
		} else {
			return (Model) objectModel;
		}
	}

	/**
	 * Obtém o objeto com a chave desejada embutido em um model.<br>
	 * Caso o objeto da chave desejada seja um {@link Model}, este será
	 * simplesmente retornado, caso contrário ele será embutido em um Model
	 * com a mesma chave requerida.
	 * 
	 * @param key
	 *            chave do model.
	 * @return {@link Model} contendo o objeto da chave.
	 */
	public Model getAsModel(String key) {
		Object objectModel = get(key);
		if (objectModel instanceof LinkedHashMap<?, ?>) {
			Model result = Models.newModel();
			result.putAll((LinkedHashMap<String, ?>) objectModel);
			return result;
		} else {
			return Models.newModel().add(key, objectModel);
		}
	}

	private Model replace(String key, Model value) {
		put(key, value);
		return this;
	}

	public ListModel getListModel(String key) {
		Object list = get(key);
		if (list instanceof ArrayList<?>) {

			ArrayList<?> arrayListModel = (ArrayList<?>) list;
			ListModel listModel = Models.newListModel();

			for (Object obj : arrayListModel) {

				String str = obj.toString();
				String[] split = str.replace("{", "").replace("}", "").replace("\"", "").replace("=", ":").split(",");

				Model newModel = Models.newModel();

				for (String string : split) {
					String[] split2 = string.split(":");
					String string2 = split2[0];
					String string3 = "";

					if (split2.length > 1)
						string3 = split2[1];

					newModel.add(string2.trim(), empty(string3) ? null : string3.trim());
				}

				listModel.add(newModel);
			}

			add(key, listModel);
			return listModel;
		} else {
			return (ListModel) list;
		}
	}

	public Model add(String key, Object value) {
		put(key, value);
		return this;
	}

	public Model copy(Model sourceModel, String sourceKey, String key) {
		add(key, sourceModel.get(sourceKey));
		return this;
	}

	public Model copy(Model sourceModel, String sourceKey) {
		copy(sourceModel, sourceKey, sourceKey);
		return this;
	}

	public Model copy(Model sourceModel) {
		putAll(sourceModel);
		return this;
	}

	public Model copy(Model sourceModel, String sourceKey, ModelFieldMap modelFieldMap) {
		return add(sourceKey, Models.newListModel().add(sourceModel.getListModel(sourceKey), modelFieldMap));
	}

	public Model newModel(String key) {
		Model result = Models.newModel();
		add(key, result);
		return result;
	}

	public ListModel newList(String key) {
		ListModel result = new ListModel();
		add(key, result);
		return result;
	}

	public void addError(MessageError error, String details) {
		ListModel errors = getListModel(ERRORS_KEY);
		if (isNull(errors)) {
			errors = newList(ERRORS_KEY);
		}
		errors.newModel().add(ERROR_CODE_KEY, error.getCode()).add(ERROR_MESSAGE_KEY, error.getMessage()).add(ERROR_DETAILS_KEY, details);
	}

	public Boolean hasErrors() {
		return containsKey(ERRORS_KEY);
	}

	public ListModel getErros() {
		if (hasErrors())
			return getListModel(ERRORS_KEY);
		return null;
	}

	public String getErrorCode() {
		return this.getStr(ERROR_CODE_KEY);
	}

	public String getErrorMessage() {
		return this.getStr(ERROR_MESSAGE_KEY);
	}

	public String getErrorDetails() {
		return this.getStr(ERROR_DETAILS_KEY);
	}

	public String getStr(String key) {
		Object object = get(key);
		if (isNull(object))
			return null;
		return object.toString();
	}

	/**
	 * Substitui apenas a chave origem pela chave destino.
	 */
	public Model replaceKey(String sourceKey, String destinationKey) {
		add(destinationKey, remove(sourceKey));
		return this;
	}

	public String toString() {
		// try {
		// return new
		// ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(this);
		// } catch (Exception e) {
		// e.printStackTrace();
		// return super.toString();
		// }
		return super.toString();
	}

	public Boolean getBoo(String key) {
		Object bool = get(key);
		if (isNull(bool)) {
			return false;
		}
		try {
			return parseBoolean(bool.toString());
		} catch (Exception e) {
			return false;
		}
	}

	public Long getLong(String key) {
		Object obj = get(key);
		if (isNull(obj)) {
			return 0l;
		}
		try {
			return parseLong(obj.toString());
		} catch (Exception e) {
			return 0l;
		}
	}

	public Float getFloat(String key) {
		Object obj = get(key);
		if (isNull(obj)) {
			return 0f;
		}
		try {
			return parseFloat(obj.toString());
		} catch (Exception e) {
			return 0f;
		}
	}

	public Integer getInt(String key) {
		Object obj = get(key);
		if (isNull(obj))
			return 0;

		try {
			return parseInt(obj.toString());
		} catch (Exception e) {
			return 0;
		}
	}

	public Double getDou(String key) {
		Object obj = get(key);
		if (isNull(obj))
			return 0d;

		try {
			return parseDouble(obj.toString());
		} catch (Exception e) {
			return 0d;
		}
	}

	public BigDecimal getBig(String key) {
		Object value = get(key);
		return value == null ? null : new BigDecimal(value.toString());
	}

	public Date getDate(String key) {
		Object obj = get(key);
		if (isNull(obj))
			return null;

		try {
			if (obj instanceof Date)
				return (Date) obj;
			if (obj instanceof Long)
				return new Date((Long) obj);
			if (obj instanceof String) {
				long time = NumberUtils.toLong(obj.toString(), 0);
				return new Date(time);
			}
			return null;
		} catch (Exception e) {
			return null;
		}
	}

	public static Model getKeys(Model parans) {
		Model model = new Model();
		int indice = 1;
		for (Entry<String, Object> entry : parans.entrySet()) {
			model.add("key" + indice, entry.getKey());
			indice++;
		}
		return model;
	}

	public static Model getValues(Model parans) {
		Model model = new Model();
		int indice = 1;
		for (Entry<String, Object> entry : parans.entrySet()) {
			model.add("values" + indice, entry.getValue());
			indice++;
		}
		return model;
	}
}
